import jwt, { SignOptions } from 'jsonwebtoken';
import { Request, Response, NextFunction } from 'express';
import { PrismaClient, Role } from '@prisma/client';
import { ResponseHelper } from '../utils/response';

const prisma = new PrismaClient();

/**
 * JWT载荷接口
 */
interface JwtPayload {
  userId: number;
  role: Role;
  openid: string;
  iat?: number;
  exp?: number;
}

/**
 * 用户信息接口（附加到Request对象）
 */
interface AuthUser {
  id: number;
  openid: string;
  username: string | null;
  nickname: string | null;
  avatarUrl: string | null;
  role: Role;
}

/**
 * 扩展Express Request接口以包含用户信息
 */
declare global {
  namespace Express {
    interface Request {
      user?: AuthUser;
    }
  }
}

/**
 * JWT认证中间件
 * 验证请求头中的Bearer token，并将用户信息添加到req.user中
 */
export const authenticateToken = async (
  req: Request,
  res: Response,
  next: NextFunction
): Promise<void> => {
  try {
    // 从请求头获取token
    const authHeader = req.headers['authorization'];
    const token = authHeader && authHeader.split(' ')[1]; // Bearer TOKEN

    if (!token) {
      ResponseHelper.unauthorized(res, '访问令牌缺失');
      return;
    }

    // 验证token
    const decoded = jwt.verify(token, process.env.JWT_SECRET as string) as JwtPayload;

    // 查询用户信息
    const user = await prisma.user.findUnique({
      where: { id: decoded.userId },
      select: {
        id: true,
        openid: true,
        username: true,
        nickname: true,
        avatarUrl: true,
        role: true
      }
    });

    if (!user) {
      ResponseHelper.unauthorized(res, '用户不存在或已被删除');
      return;
    }

    // 将用户信息添加到请求对象中
    req.user = user;

    next();
  } catch (error: any) {
    console.error('Token验证失败:', error.message);

    if (error.name === 'JsonWebTokenError') {
      ResponseHelper.unauthorized(res, '无效的访问令牌');
      return;
    }

    if (error.name === 'TokenExpiredError') {
      ResponseHelper.unauthorized(res, '访问令牌已过期，请重新登录');
      return;
    }

    ResponseHelper.serverError(res, '令牌验证失败', error);
  }
};

/**
 * 可选的JWT认证中间件
 * 如果有token则验证并添加用户信息，没有token则继续执行
 */
export const optionalAuth = async (
  req: Request,
  res: Response,
  next: NextFunction
): Promise<void> => {
  try {
    const authHeader = req.headers['authorization'];
    const token = authHeader && authHeader.split(' ')[1];

    if (token) {
      const decoded = jwt.verify(token, process.env.JWT_SECRET as string) as JwtPayload;

      const user = await prisma.user.findUnique({
        where: { id: decoded.userId },
        select: {
          id: true,
          openid: true,
          username: true,
          nickname: true,
          avatarUrl: true,
          role: true
        }
      });

      if (user) {
        req.user = user;
      }
    }

    next();
  } catch (error) {
    // 可选认证失败时不返回错误，继续执行
    next();
  }
};

/**
 * 管理员权限检查中间件
 * 必须在authenticateToken之后使用
 */
export const requireAdmin = (
  req: Request,
  res: Response,
  next: NextFunction
): void => {
  if (!req.user) {
    ResponseHelper.unauthorized(res, '请先登录');
    return;
  }

  if (req.user.role !== 'ADMIN') {
    ResponseHelper.forbidden(res, '仅管理员可访问此资源');
    return;
  }

  next();
};

/**
 * 生成JWT token
 * @param userId 用户ID
 * @param role 用户角色
 * @param openid 用户openid
 * @param expiresIn 过期时间
 * @returns JWT token
 */
export const generateToken = (
  userId: number,
  role: Role,
  openid: string,
  expiresIn: string = process.env.JWT_EXPIRES_IN || '7d'
): string => {
  const payload: JwtPayload = {
    userId,
    role,
    openid
  };

  const options: SignOptions = {
    expiresIn: expiresIn as any
  };

  return jwt.sign(payload, process.env.JWT_SECRET as string, options);
};

/**
 * 验证JWT token
 * @param token JWT token
 * @returns 解码后的载荷
 */
export const verifyToken = (token: string): JwtPayload => {
  return jwt.verify(token, process.env.JWT_SECRET as string) as JwtPayload;
};

/**
 * 从token字符串中解析载荷（不验证）
 * @param token JWT token
 * @returns 解码后的载荷（未验证）
 */
export const decodeToken = (token: string): JwtPayload | null => {
  try {
    return jwt.decode(token) as JwtPayload;
  } catch (error) {
    return null;
  }
};
